﻿using Kernelx.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

namespace Kernelx.Contexts.Defaults.Sequences._Internals
{
    using ParameterOutput = ParameterBracket.ParameterOutput;
    using SequeOutput = SequeBracket.SequeOutput;

    internal partial class SequeGenerator : ISequeGenerator
    {

        private readonly IDictionary<string, TargetOptions> _container;

        private readonly IDictionary<string, BaseBracket> _bracketContainer;

        #region 根据外部注册的信息初始化序列生成器

        public SequeGenerator(Dictionary<string, string> formats, Dictionary<string, GetMaxID> getMaxIds, IList<BaseBracket> brackets)
        {

            _bracketContainer = new Dictionary<string, BaseBracket>();
            foreach (var bracket in GetPreBracket())
                _bracketContainer[bracket.Name] = bracket;
            foreach (var bracket in brackets)
                _bracketContainer[bracket.Name] = bracket;


            _container = new Dictionary<string, TargetOptions>(formats.Count);
            foreach (var target in formats.Keys)
                _container[target] = FormatTransfer(target, formats[target], getMaxIds[target]);

        }

        private IEnumerable<BaseBracket> GetPreBracket()
        {
            var type = typeof(BaseBracket);
            var flist = Assembly.GetAssembly(type).GetTypes().Where(x => x.IsDerived(type));
            foreach (var f in flist)
            {
                var c = f.GetConstructor(Type.EmptyTypes);
                var obj = c.Invoke(new object[0]) as BaseBracket;
                yield return obj;
            }
        }

        private const string _pattern = @"\{(?<name>[\w-]+):(?<formula>[^\}]+)\}";
        private TargetOptions FormatTransfer(string target, string format, GetMaxID getMaxID)
        {
            var options = new TargetOptions { Format = format, GetMaxID = getMaxID };

            var index = 0;
            var match = Regex.Match(format, _pattern);
            while (match.Success)
            {
                var ruleStr = match.Groups[0].Value;
                var name = match.Groups["name"].Value;
                var formula = match.Groups["formula"].Value;
                if (_bracketContainer.ContainsKey(name))
                {
                    var bracket = _bracketContainer[name];
                    var flag = false;
                    if (formula.EndsWith(ParameterBracket.Label))
                    {
                        formula = formula.Substring(0, formula.Length - ParameterBracket.Label.Length);
                        flag = true;
                    }
                    var output = bracket.Input(formula);
                    if (output is SequeOutput seq)
                    {
                        if (options.SequeOutput == null)
                        {
                            options.Format = options.Format.Replace(ruleStr, SequeBracket.Slot);
                            options.SequeOutput = seq;
                        }
                        else
                            throw new ApplicationException($"目标[{target}]定义的 {format} 当前中的规则段中[{SequeBracket._Name}]不唯一");

                    }
                    else if (output is ParameterOutput param)
                    {
                        options.Format = options.Format.Replace(ruleStr, param.Slot);
                        options.ParameterOutputs.Add(param);
                    }
                    else
                    {
                        options.Format = options.Format.Replace(ruleStr, $"{{{index++}}}");
                        options.NormalOutputs.Append(name, output, flag);
                    }
                    match = match.NextMatch();
                }
                else
                    throw new ApplicationException($"目标[{target}]定义的 {format} 当前中的规则段中名为[{name}]的无法识别");
            }
            return options.SequeOutput != null ? options : throw new ApplicationException("必须包含{seq:length}");
        }

        #endregion

        #region 序列生成器的实现方法

        public bool IsRegistered(string target) => _container.ContainsKey(target);

        public string Get(string target, object args = null)
        {
            if (_container.ContainsKey(target))
            {
                var options = _container[target];
                lock (options)
                {
                    var nvs = options.NormalOutputs.Fetch();
                    var result = string.Format(options.Format, nvs.Values.ToArray());
                    var arguments = new Dictionary<string, string>();
                    if (options.ParameterOutputs.Count > 0)
                    {
                        var seek = SeekBy(args);
                        foreach (var output in options.ParameterOutputs)
                        {
                            var value = seek(output.ParaName) ?? throw new ApplicationException($"无法从args对象中提取参数名[{output.ParaName}]的值");
                            value = output.Checkup(value).Get();

                            result = result.Replace(output.Slot, value);
                            arguments[output.ParaName] = value;
                        }
                    }

                    foreach (var name in options.NormalOutputs.Args)
                        arguments[name] = nvs[name];

                    if (!options.Seques.Contains(result))
                    {
                        var maxId = options.GetMaxID(WorkloadStartupContext.Current, arguments);
                        var seque = GetCurrentSeque(maxId, result.Replace(SequeBracket.Slot, options.SequeOutput.Pattern));
                        options.Seques[result] = seque;
                    }
                    var seqstr = options.SequeOutput.Seed(options.Seques[result]++).Get();
                    return result.Replace(SequeBracket.Slot, seqstr);
                }

            }
            throw new ApplicationException($"{target}的生成规则未注册,无法获取最新序列值");
        }

#if DEBUG
        /// <summary>
        /// 将外部传入的args对象转成按name取值的提取器
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>  
#endif
        private Func<string, string> SeekBy(object args)
        {
            IDictionary<string, string> srouce = null;
            if (args != null)
                srouce = (args is IDictionary<string, string> dic) ? dic : _tranfer(args);

            return name => srouce != null && srouce.ContainsKey(name) ? srouce[name] : null;

            IDictionary<string, string> _tranfer(object obj)
            {
                var props = obj.GetType().GetProperties();
                if (props.Length > 0)
                {
                    var result = new Dictionary<string, string>();
                    foreach (var p in props)
                        result[p.Name] = p.GetValue(obj, null).ToString();
                    return result;
                }
                return null;
            }
        }

        private int GetCurrentSeque(string maxId, string pattern)
        {
            if (!string.IsNullOrEmpty(maxId))
            {
                var match = Regex.Match(maxId, pattern);
                return match.Success ? Convert.ToInt32(match.Groups[SequeBracket._Name].Value) + 1 : 1;
            }
            return 1;
        }

        #endregion

    }
}
